﻿

namespace Stee.CAP8.MessageBus.SampleUI.ViewModels
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Text;
    using System.Threading;
    using Microsoft.Extensions.DependencyInjection;
    using NLog;
    using Stee.CAP8.MessageBus.Interface.Interfaces;
    using Stee.CAP8.MessageBus.Interface.Models;
    using Stee.CAP8.MessageBus.Interface.Utility;
    using Stee.CAP8.MessageBus.Kafka.Publisher;
    using Stee.CAP8.MessageBus.Kafka.Subscriber;
    using Stee.CAP8.MessageBus.SampleUI.Models;

    public class MessageViewModel : INotifyPropertyChanged
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();


        #region Topic Information

        private const string topic = "first_topic";
        private const string key = "first_key";

        private const string topic2 = "second_topic";
        private const string key2 = "second_key";

        private const string topic3 = "third_topic";
        private const string key3 = "third_key";

        private const string topic4 = "fourth_topic";
        private const string key4 = "fourth_key";

        private readonly TopicName topicNameString = new TopicName(topic, key);
        private readonly TopicName topicNameObject = new TopicName(topic2, key2);
        private readonly TopicName topicNameCommand = new TopicName(topic3, key3);
        private readonly TopicName topicNameEvent = new TopicName(topic4, key4);

        #endregion Topic Information


        #region Publisher & Subscriber

        private IPublisher stringPublisher;
        private IPublisher objectPublisher;
        private IPublisher commandPublisher;
        private IPublisher eventPublisher;
        private CancellationTokenSource stringPublishToken;
        private CancellationTokenSource objectPublishToken;
        private CancellationTokenSource commandPublishToken;
        private CancellationTokenSource eventPublishToken;

        private ISubscriber stringSubscriber;
        private ISubscriber objectSubscriber;
        private ISubscriber commandSubscriber;
        private ISubscriber eventSubscriber;

        #endregion Publisher & Subscriber


        #region Service Provider & Others

        private ServiceProvider serviceProvider;

        #endregion Service Provider & Others


        private string textBlockResult;

        /// <summary>
        /// Gets or sets the text block string
        /// </summary>
        public string TextBlockResult
        {
            get { return this.textBlockResult; }
            set
            {
                this.textBlockResult = value;
                this.NotifyPropertyChanged(nameof(this.TextBlockResult));
            }
        }


        /// <summary>
        /// Initializes a new instance of the MessageViewModel class
        /// </summary>
        public MessageViewModel()
        {
            this.TextBlockResult = string.Empty;
            this.InitializeServiceProvider();
            this.CreateSubscription();
        }

        ~MessageViewModel()
        {
            this.stringSubscriber.Dispose();
            this.objectSubscriber.Dispose();
            this.commandSubscriber.Dispose();
            this.eventSubscriber.Dispose();

            this.stringPublisher.Dispose();
            this.objectPublisher.Dispose();
            this.commandPublisher.Dispose();
            this.eventPublisher.Dispose();
        }

        /// <summary>
        /// Initializes the service provider in creating publisher & subscriber
        /// </summary>
        private void InitializeServiceProvider()
        {
            IServiceCollection serviceCollection = new ServiceCollection();

            //// Add publisher scope
            serviceCollection.AddTransient<IPublisher, PublisherImpl>();

            //// Add subscriber scope
            serviceCollection.AddTransient<ISubscriber, SubscriberImpl>();

            //// Create service provider
            this.serviceProvider = serviceCollection.BuildServiceProvider();
        }

        /// <summary>
        /// Create subscriptions
        /// </summary>
        private void CreateSubscription()
        {
            this.SubscribeMessageString();
            this.SubscribeMessageObject();
            this.SubscribeMessageCommand();
            this.SubscribeMessageEvent();
        }

        /// <summary>
        /// Send new messages
        /// </summary>
        public void PublishNewMessages()
        {
            try
            {
                this.TextBlockResult = $"{this.TextBlockResult}=========={Environment.NewLine}";

                this.PublishMessageTypeString();
                this.PublishMessageTypeObject();
                this.PublishCommandMessage();
                this.PublishEventMessage();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, ex.Message);
            }
        }

        /// <summary>
        /// Clear messages
        /// </summary>
        public void ClearMessages()
        {
            try
            {
                this.TextBlockResult = string.Empty;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, ex.Message);
            }
        }


        #region Publish

        /// <summary>
        /// Publish messages of string data type
        /// </summary>
        private void PublishMessageTypeString()
        {
            this.stringPublisher = this.serviceProvider.GetService<IPublisher>();
            this.stringPublisher.Initialize(this.OnMessagePublished, this.OnPublisherError);

            if (this.stringPublisher.IsInitialized)
            {
                string message = RandomString(5);
                this.stringPublishToken = new CancellationTokenSource();
                Dictionary<string, string> properties = new Dictionary<string, string>
                {
                    { "Name", "SomeName" },
                    { "Object1", JsonSerializer.SerializeString(DateTime.Now) },
                    { "Object2", JsonSerializer.SerializeString(Guid.NewGuid()) },
                };

                if (this.stringPublisher.Publish(this.topicNameString, message, properties, this.stringPublishToken.Token))
                {
                    string msg = $"Publish string => {message}";
                    this.TextBlockResult = this.TextBlockResult + $"{msg}{Environment.NewLine}";
                    Console.WriteLine(msg);
                }
                else
                {
                    string msg = $"[Error] Publish string failed => {message}";
                    this.TextBlockResult = this.TextBlockResult + $"{msg}{Environment.NewLine}";
                    Console.WriteLine(msg);
                }
            }

            this.stringPublishToken.Cancel();
        }

        /// <summary>
        /// Publish messages of object data type
        /// </summary>
        private void PublishMessageTypeObject()
        {
            this.objectPublisher = this.serviceProvider.GetService<IPublisher>();
            this.objectPublisher.Initialize(this.OnPersonPublished, this.OnPublisherError);

            if (this.objectPublisher.IsInitialized)
            {
                Person person = new Person(RandomString(5), RandomNumber(20, 50));
                this.objectPublishToken = new CancellationTokenSource();

                if (this.objectPublisher.Publish(this.topicNameObject, person, this.objectPublishToken.Token))
                {
                    string msg = $"Publish Person object => {person}";
                    this.TextBlockResult = this.TextBlockResult + $"{msg}{Environment.NewLine}";
                    Console.WriteLine(msg);
                }
                else
                {
                    string msg = $"[Error] Publish Person object failed => {person}";
                    this.TextBlockResult = this.TextBlockResult + $"{msg}{Environment.NewLine}";
                    Console.WriteLine(msg);
                }
            }

            this.objectPublishToken.Cancel();
        }

        /// <summary>
        /// Publish command message
        /// </summary>
        private async void PublishCommandMessage()
        {
            this.commandPublisher = this.serviceProvider.GetService<IPublisher>();
            this.commandPublisher.Initialize(this.OnCommandPublished, this.OnPublisherError);

            if (this.commandPublisher.IsInitialized)
            {
                CommandMessage commandMsg = new CommandMessage
                {
                    ReplyChannel = "MyChannel",
                    ClientID = "MyClientID",
                    CorrelationID = "MyCorrelationID",
                    CreateDT = DateTime.Now,
                    Data = JsonSerializer.Serialize("MyData"),
                    JWT = "MyJWT",
                    RecordID = "MyRecordID",
                    Action = "MyReplyTopic",
                    Source = "MySource",
                };
                commandMsg.AddHeader("KeyCommand", "ValueCommand");

                this.commandPublishToken = new CancellationTokenSource();

                if (await this.commandPublisher.PublishCommandAsync(this.topicNameCommand, commandMsg, this.commandPublishToken.Token))
                {
                    string msg = $"Publish CommandMessage object => {commandMsg}";
                    this.TextBlockResult = $"{this.TextBlockResult}{msg}{Environment.NewLine}";
                    Console.WriteLine(msg);
                }
                else
                {
                    string msg = $"[Error] Publish CommandMessage object failed => {commandMsg}";
                    this.TextBlockResult = $"{this.TextBlockResult}{msg}{Environment.NewLine}";
                    Console.WriteLine(msg);
                }
            }

            this.commandPublishToken.Cancel();
        }

        /// <summary>
        /// Publish event message
        /// </summary>
        private async void PublishEventMessage()
        {
            this.eventPublisher = this.serviceProvider.GetService<IPublisher>();
            this.eventPublisher.Initialize(this.OnEventPublished, this.OnPublisherError);

            if (this.eventPublisher.IsInitialized)
            {
                EventMessage eventMsg = new EventMessage
                {
                    ClientID = "MyClientID",
                    CorrelationID = "MyCorrelationID",
                    CreateDT = DateTime.Now,
                    Data = JsonSerializer.Serialize("MyData"),
                    JWT = "MyJWT",
                    Action = "MyReplyTopic",
                    Source = "MySource",
                };
                eventMsg.AddHeader("KeyEvent", "ValueEvent");

                this.eventPublishToken = new CancellationTokenSource();

                if (await this.eventPublisher.PublishEventAsync(this.topicNameEvent, eventMsg, this.eventPublishToken.Token))
                {
                    string msg = $"Publish EventMessage object => {eventMsg}";
                    this.TextBlockResult = $"{this.TextBlockResult}{msg}{Environment.NewLine}";
                    Console.WriteLine(msg);
                }
                else
                {
                    string msg = $"[Error] Publish EventMessage object failed => {eventMsg}";
                    this.TextBlockResult = $"{this.TextBlockResult}{msg}{Environment.NewLine}";
                    Console.WriteLine(msg);
                }
            }

            this.eventPublishToken.Cancel();
        }

        #endregion Publish


        #region Publish Handlers

        /// <summary>
        /// Mock example method name format, "On<Type><Action>".
        /// Publish response handler for string data type messages.
        /// </summary>
        /// <param name="publishOutcome">Message publish outcome</param>
        /// <returns>Return TRUE if handled successfully else FALSE</returns>
        private bool OnMessagePublished(PublishOutcome publishOutcome)
        {
            bool isHandled = false;

            if (publishOutcome != null)
            {
                //// Handle your publish outcome here


                isHandled = true;
            }

            return isHandled;
        }

        /// <summary>
        /// Mock example method name format, "On<Type><Action>".
        /// Publish response handler for Person data type messages.
        /// </summary>
        /// <param name="publishOutcome">Message publish outcome</param>
        /// <returns>Return TRUE if handled successfully else FALSE</returns>
        private bool OnPersonPublished(PublishOutcome publishOutcome)
        {
            bool isHandled = false;

            if (publishOutcome != null)
            {
                //// Handle your publish outcome here


                isHandled = true;
            }

            return isHandled;
        }

        /// <summary>
        /// Mock example method name format, "On<Type><Action>".
        /// Publish response handler for command data type messages.
        /// </summary>
        /// <param name="publishOutcome">Message publish outcome</param>
        /// <returns>Return TRUE if handled successfully else FALSE</returns>
        private bool OnCommandPublished(PublishOutcome publishOutcome)
        {
            bool isHandled = false;

            if (publishOutcome != null)
            {
                //// Handle your publish outcome here


                isHandled = true;
            }

            return isHandled;
        }

        /// <summary>
        /// Mock example method name format, "On<Type><Action>".
        /// Publish response handler for event data type messages.
        /// </summary>
        /// <param name="publishOutcome">Message publish outcome</param>
        /// <returns>Return TRUE if handled successfully else FALSE</returns>
        private bool OnEventPublished(PublishOutcome publishOutcome)
        {
            bool isHandled = false;

            if (publishOutcome != null)
            {
                //// Handle your publish outcome here


                isHandled = true;
            }

            return isHandled;
        }

        /// <summary>
        /// Mock example method name format, "On<Action><Response>".
        /// Publish error handler for broker publish error.
        /// </summary>
        /// <param name="errorCode">Publish error code</param>
        /// <param name="isFatal">Flag indicating fatal error</param>
        private void OnPublisherError(string errorCode, bool isFatal)
        {
            if (string.IsNullOrEmpty(errorCode))
            {
                Logger.Warn($"Unexpected publisher error, no error code returned. Is fatal: {isFatal}");
            }
            else
            {
                Logger.Error($"Publisher error: {errorCode}, Is fatal: {isFatal}");
            }
        }

        #endregion Publish Handlers


        #region Subscribe

        /// <summary>
        /// Subscribe for messages of string data type
        /// </summary>
        private void SubscribeMessageString()
        {
            this.stringSubscriber = this.serviceProvider.GetService<ISubscriber>();
            this.stringSubscriber.Initialize(this.topicNameString, this.OnMessageReceived, this.OnSubscriberError);

            if (this.stringSubscriber.IsInitialized)
            {
                this.stringSubscriber.Subscribe();
            }
        }

        /// <summary>
        /// Subscribe for messages of object data type
        /// </summary>
        private void SubscribeMessageObject()
        {
            this.objectSubscriber = this.serviceProvider.GetService<ISubscriber>();
            this.objectSubscriber.Initialize(this.topicNameObject, this.OnPersonReceived, this.OnSubscriberError);

            if (this.objectSubscriber.IsInitialized)
            {
                this.objectSubscriber.Subscribe();
            }
        }

        /// <summary>
        /// Subscribe for messages of command message data type
        /// </summary>
        private void SubscribeMessageCommand()
        {
            this.commandSubscriber = this.serviceProvider.GetService<ISubscriber>();
            this.commandSubscriber.Initialize(this.topicNameCommand, this.OnCommandReceived, this.OnSubscriberError);

            if (this.commandSubscriber.IsInitialized)
            {
                this.commandSubscriber.Subscribe();
            }
        }

        /// <summary>
        /// Subscribe for messages of event message data type
        /// </summary>
        private void SubscribeMessageEvent()
        {
            this.eventSubscriber = this.serviceProvider.GetService<ISubscriber>();
            this.eventSubscriber.Initialize(this.topicNameEvent, this.OnEventReceived, this.OnSubscriberError);

            if (this.eventSubscriber.IsInitialized)
            {
                this.eventSubscriber.Subscribe();
            }
        }

        #endregion Subscribe


        #region Subscribe Handlers

        /// <summary>
        /// Mock example method name format, "On<Type><Event or Command>".
        /// Handle message of string data type
        /// </summary>
        /// <param name="topicName">Topic name</param>
        /// <param name="message">Message, string type</param>
        /// <param name="properties">Message meta-data/properties</param>
        /// <returns>Return TRUE if consumed successfully else FALSE</returns>
        private bool OnMessageReceived(TopicName topicName, string messageType, byte[] message, IDictionary<string, byte[]> properties)
        {
            try
            {
                if (topicName == null)
                {
                    throw new ArgumentNullException(nameof(topicName));
                }

                //// Handle / process messages here
                //// Consume message data type, string
                string originalMessage = JsonSerializer.Deserialize<string>(message);
                string output = $"Consumed message <string>: {originalMessage}, Properties: ";

                output += $"{JsonSerializer.Deserialize<string>(properties["Name"])}, ";
                var dtStr = JsonSerializer.Deserialize<string>(properties["Object1"]);
                output += $"{JsonSerializer.DeserializeString<DateTime>(dtStr)}, ";
                var guid = JsonSerializer.Deserialize<string>(properties["Object2"]);
                output += $"{JsonSerializer.DeserializeString<Guid>(guid)}";

                this.TextBlockResult = $"{this.TextBlockResult}{output}{Environment.NewLine}";
                Console.WriteLine(output);
                Logger.Info("Message consumed successfully.");

                return true;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Mock example method name format, "On<Type><Event or Command>".
        /// Handle message of object data type
        /// </summary>
        /// <param name="topicName">Topic name</param>
        /// <param name="message">Message, object type</param>
        /// <param name="properties">Message meta-data/properties</param>
        /// <returns>Return TRUE if consumed successfully else FALSE</returns>
        private bool OnPersonReceived(TopicName topicName, string messageType, byte[] message, IDictionary<string, byte[]> properties)
        {
            try
            {
                if (topicName == null)
                {
                    throw new ArgumentNullException(nameof(topicName));
                }

                //// Consume data type, object
                Person originalMessage = JsonSerializer.Deserialize<Person>(message);
                string output = $"Consumed message <Person>: {originalMessage}";

                this.TextBlockResult = $"{this.TextBlockResult}{output}{Environment.NewLine}";
                Console.WriteLine(output);
                Logger.Info("Message consumed successfully.");

                return true;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Handle message of CommandMessage type
        /// </summary>
        /// <param name="topicName">Topic name</param>
        /// <param name="commandMessage">Command message</param>
        /// <returns>Return TRUE if consumed successfully else FALSE</returns>
        private bool OnCommandReceived(TopicName topicName, CommandMessage commandMessage)
        {
            try
            {
                if (topicName == null)
                {
                    throw new ArgumentNullException(nameof(topicName));
                }
                else if (commandMessage == null)
                {
                    throw new ArgumentNullException(nameof(commandMessage));
                }

                //// Consume command message type
                string output = $"Consumed command message <CommandMessage>: {commandMessage}";

                this.TextBlockResult = $"{this.TextBlockResult}{output}{Environment.NewLine}";
                Console.WriteLine(output);
                Logger.Info("Message consumed successfully.");

                return true;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Handle message of EventMessage type
        /// </summary>
        /// <param name="topicName">Topic name</param>
        /// <param name="eventMessage">Event message</param>
        /// <returns>Return TRUE if consumed successfully else FALSE</returns>
        private bool OnEventReceived(TopicName topicName, EventMessage eventMessage)
        {
            try
            {
                if (topicName == null)
                {
                    throw new ArgumentNullException(nameof(topicName));
                }
                else if (eventMessage == null)
                {
                    throw new ArgumentNullException(nameof(eventMessage));
                }

                //// Consume event message type
                string output = $"Consumed event message <EventMessage>: {eventMessage}";

                this.TextBlockResult = $"{this.TextBlockResult}{output}{Environment.NewLine}";
                Console.WriteLine(output);
                Logger.Info("Message consumed successfully.");

                return true;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Mock example method name format, "On<Action><Response>".
        /// Error handler for subscriber.
        /// </summary>
        /// <param name="errorCode">Broker error code</param>
        /// <param name="isFatal">Flag indicating fatal error</param>
        private void OnSubscriberError(string errorCode, bool isFatal)
        {
            if (string.IsNullOrEmpty(errorCode))
            {
                Logger.Warn($"Unexpected subscriber error, no error code returned. Is fatal: {isFatal}");
            }
            else
            {
                Logger.Error($"Subscriber error: {errorCode}, Is fatal: {isFatal}");
            }
        }

        #endregion Subscribe Handlers


        #region Static Methods

        /// <summary>
        /// Generate random number
        /// </summary>
        /// <param name="min">Minimum value</param>
        /// <param name="max">Maximum value</param>
        /// <returns>Return random number</returns>
        public static int RandomNumber(int min = 0, int max = 100)
        {
            Random random = new Random();
            return random.Next(min, max);
        }

        /// <summary>
        /// Generate a random string with a given size
        /// </summary>
        /// <param name="size">String length</param>
        /// <returns>Return a new string</returns>
        public static string RandomString(int size = 5)
        {
            StringBuilder builder = new StringBuilder();
            Random random = new Random();
            char ch;

            for (int i = 0; i < size; i++)
            {
                ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
                builder.Append(ch);
            }

            return builder.ToString();
        }

        #endregion Static Methods


        #region MVVM Property Changed Handler

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(String info)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
        }

        #endregion MVVM Property Changed Handler
    }
}
